package com.example.user.service.application.service;


import com.example.user.service.common.JwtUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Token黑名单服务类 - JWT令牌失效管理
 * 
 * 这个服务类解决了JWT无状态认证中的一个重要问题：如何在用户登出后立即使令牌失效
 * 
 * JWT的无状态特性说明：
 * - JWT令牌是自包含的，包含了所有必要的用户信息
 * - 服务端不需要存储会话状态，每次请求都通过验证令牌签名来确认身份
 * - 这种设计的优点是可扩展性好，但缺点是无法在服务端主动"销毁"令牌
 * - 令牌在过期时间之前始终有效，即使用户已经登出
 * 
 * 黑名单机制原理：
 * 1. 维护一个已失效令牌的黑名单列表
 * 2. 用户登出时，将令牌添加到黑名单
 * 3. 每次验证令牌时，先检查是否在黑名单中
 * 4. 如果在黑名单中，则拒绝访问，即使令牌本身是有效的
 * 
 * 存储方案选择：
 * - 内存存储（ConcurrentHashMap）：适合单机部署，性能最好
 * - Redis存储：适合分布式部署，多个服务实例共享黑名单
 * - 数据库存储：适合对数据持久性要求高的场景
 * 
 * 本实现使用内存存储，具有以下特点：
 * - 高性能：内存访问速度快，不涉及网络IO
 * - 线程安全：使用ConcurrentHashMap保证并发安全
 * - 自动清理：定时清理过期的黑名单记录，避免内存泄漏
 * - 简单可靠：无外部依赖，部署简单
 * 
 * 注意事项：
 * - 重启服务会丢失黑名单数据，已登出的用户令牌可能重新生效
 * - 多实例部署时，各实例的黑名单不共享
 * - 如需解决以上问题，建议改用Redis等外部存储
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TokenBlacklistService {

    /**
     * JWT工具类，用于解析令牌获取过期时间
     */
    private final JwtUtil jwtUtil;

    /**
     * 黑名单存储容器
     * 
     * 使用ConcurrentHashMap确保线程安全的并发访问
     * Key: JWT令牌字符串
     * Value: 令牌的过期时间，用于定时清理
     * 
     * 为什么存储过期时间：
     * - 避免永久存储已过期的令牌，节省内存空间
     * - 支持定时清理机制，自动移除不再需要的黑名单记录
     * - 提供调试信息，便于排查问题
     */
    private final ConcurrentHashMap<String, Date> blacklistedTokens = new ConcurrentHashMap<>();

    /**
     * 将令牌添加到黑名单
     * 
     * 当用户登出时调用此方法，将JWT令牌加入黑名单
     * 加入黑名单后，该令牌将无法再用于身份验证
     * 
     * 实现细节：
     * 1. 解析令牌获取过期时间
     * 2. 将令牌和过期时间存入黑名单Map
     * 3. 记录操作日志，便于审计和调试
     * 
     * 异常处理：
     * - 如果令牌格式无效，会记录警告日志但不抛出异常
     * - 确保即使个别令牌处理失败，也不影响整体功能
     * 
     * @param token JWT令牌字符串
     */
    public void addToBlacklist(String token) {
        try {
            // 解析令牌获取过期时间
            // 这样可以在令牌自然过期后自动清理黑名单记录
            Date expirationTime = jwtUtil.getExpirationDateFromToken(token);
            
            // 将令牌添加到黑名单
            blacklistedTokens.put(token, expirationTime);
            
            // 记录操作日志（只记录令牌的前几位，避免泄露完整令牌）
            log.info("令牌已添加到黑名单，过期时间: {}, 令牌前缀: {}...", 
                    expirationTime, 
                    token.substring(0, Math.min(token.length(), 10)));
                    
        } catch (Exception e) {
            // 如果解析令牌失败，记录警告日志
            log.warn("添加令牌到黑名单时发生错误: {}, 令牌前缀: {}...", 
                    e.getMessage(), 
                    token.substring(0, Math.min(token.length(), 10)));
        }
    }

    /**
     * 检查令牌是否在黑名单中
     * 
     * 在JWT认证过滤器中调用此方法，检查令牌是否已被列入黑名单
     * 如果令牌在黑名单中，则应拒绝该请求的访问
     * 
     * 性能考虑：
     * - ConcurrentHashMap的containsKey操作时间复杂度为O(1)
     * - 即使黑名单中有大量令牌，查询性能也很好
     * - 无需额外的网络请求或磁盘IO
     * 
     * @param token JWT令牌字符串
     * @return boolean true表示令牌在黑名单中（应拒绝访问），false表示不在黑名单中
     */
    public boolean isBlacklisted(String token) {
        boolean isBlacklisted = blacklistedTokens.containsKey(token);
        
        if (isBlacklisted) {
            log.debug("检测到黑名单令牌访问尝试，令牌前缀: {}...", 
                    token.substring(0, Math.min(token.length(), 10)));
        }
        
        return isBlacklisted;
    }

    /**
     * 定时清理过期的黑名单令牌
     * 
     * 使用Spring的@Scheduled注解实现定时任务
     * 每小时执行一次清理操作，移除已经自然过期的令牌
     * 
     * 清理的必要性：
     * - 避免内存泄漏：长期运行的服务会积累大量过期令牌
     * - 提高性能：减少黑名单大小，提高查询效率
     * - 节省资源：释放不再需要的内存空间
     * 
     * 清理策略：
     * - 只清理已经过期的令牌（当前时间 > 令牌过期时间）
     * - 使用迭代器安全地删除元素，避免并发修改异常
     * - 记录清理统计信息，便于监控和调试
     * 
     * 定时配置说明：
     * - fixedRate = 3600000：每3600000毫秒（1小时）执行一次
     * - 可以根据实际需求调整清理频率
     * - 频率过高会增加CPU开销，频率过低会占用更多内存
     */
    @Scheduled(fixedRate = 3600000) // 每小时执行一次
    public void cleanupExpiredTokens() {
        Date now = new Date();
        int initialSize = blacklistedTokens.size();
        
        // 使用removeIf方法安全地移除过期令牌
        // 这个方法是线程安全的，不会与其他操作产生冲突
        blacklistedTokens.entrySet().removeIf(entry -> {
            Date expirationTime = entry.getValue();
            return expirationTime != null && now.after(expirationTime);
        });
        
        int finalSize = blacklistedTokens.size();
        int cleanedCount = initialSize - finalSize;
        
        if (cleanedCount > 0) {
            log.info("黑名单清理完成，清理了 {} 个过期令牌，当前黑名单大小: {}", cleanedCount, finalSize);
        } else {
            log.debug("黑名单清理完成，无过期令牌需要清理，当前黑名单大小: {}", finalSize);
        }
    }

    /**
     * 获取当前黑名单大小
     * 
     * 提供监控和调试功能，可以了解当前黑名单的使用情况
     * 
     * @return int 黑名单中令牌的数量
     */
    public int getBlacklistSize() {
        return blacklistedTokens.size();
    }

    /**
     * 清空所有黑名单令牌
     * 
     * 提供管理功能，在特殊情况下可以清空整个黑名单
     * 注意：此操作会使所有已登出用户的令牌重新生效
     * 
     * 使用场景：
     * - 系统维护时需要重置黑名单状态
     * - 测试环境中需要快速清理数据
     * - 紧急情况下需要恢复所有用户访问
     */
    public void clearBlacklist() {
        int size = blacklistedTokens.size();
        blacklistedTokens.clear();
        log.warn("黑名单已被清空，共清理了 {} 个令牌", size);
    }
}